//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <limits.h>
#include <_pubcrt.h>
#include <Elasysapi.h>

_ELASTOS_NAMESPACE_USING

#include "CConsoleIo.h"
#include "CConsoleFile.h"

EXTERN_C void CDECL initenv(wchar_t **ppwszEnvBase);
EXTERN_C void CDECL freeenviron();
ELAPI  _EzGetEnvBase(wchar_t ***pppwszEnvBase, wchar_t **ppwszDevpath);
EXTERN_C wchar_t ** __p__g_DevpathBase();
#define g_DevpathBase (*__p__g_DevpathBase())

ECode CConsoleIo::Init()
{
    ECode ec;
    wchar_t **ppwszEnvBase;

    m_pDiskMgr = NULL;

    _EzGetEnvBase(&ppwszEnvBase, &g_DevpathBase);
    initenv(ppwszEnvBase);
    if (!GetEnv("CD")) {
        char env[128];
        AStringBuf_<64> diskName;
        ec = CProfile::GetAString("Info", "DefaultDisk", &diskName);
        if (ec != NOERROR) {
            assert(0 && "Error! Either lost elastos.cfg or wrong argument.");
            return ec;
        }
        else {
            strcpy(env, "CD=");
            strcat(env, diskName);
            strcat(env, "/");
            int len = strlen(env) + 1;
            wchar_t *wcsenv = (wchar_t *)_alloca(len * sizeof(wchar_t));
            if (!wcsenv) {
                assert(0 && "Alloca Error!");
                return E_OUT_OF_MEMORY;
            }

            _strtowcs(wcsenv, env);

            ECode ec = PutEnv(env, wcsenv);
            if (FAILED(ec)) {
                assert(0 && "PutEnv Error!");
                return ec;
            }
        }
    }

    for (int fd = 0; fd < 3; fd++) {
        if (!IsRedirect(fd)) {
#if defined(_win32) || defined(_wince) || defined(_linux)
            m_pIoFile[fd] = (IAbsoluteIO *)new CConsoleFile(fd);
#else
            m_pIoFile[fd] = (IAbsoluteIO *)new CConsoleFile();
#endif
            if (!m_pIoFile[fd]) {
                assert(0 && "CConsoleIo init: can't new CConsoleFile!\n");
                return E_OUT_OF_MEMORY;
            }
            m_pIoFile[fd]->AddRef();
            m_IsConsole[fd] = TRUE;
        }
        m_Offset[fd] = 0;
    }

    return NOERROR;
}

EXTERN_C IMutex ** __p__g_pEnvMutex();
#define g_pEnvMutex (*__p__g_pEnvMutex())

ECode CConsoleIo::Uninit()
{
    for (int fd = 0; fd < 3; fd++) {
        if (m_pIoFile[fd]) {
            m_pIoFile[fd]->Release();
            m_pIoFile[fd] = NULL;
        }
    }

    if (m_pDiskMgr) {
        m_pDiskMgr->Release();
        m_pDiskMgr = NULL;
    }

    if (g_pEnvMutex) {
        g_pEnvMutex->Release();
        g_pEnvMutex = NULL;
    }

    freeenviron();

    return NOERROR;
}

ECode CConsoleIo::SetIo(UInt32 fd, WString ws)
{
    ECode ec = _EzSetStdDevice(fd, ws);
    if (FAILED(ec)) {
        return ec;
    }
    if (m_pIoFile[fd]) m_pIoFile[fd]->Release();

    if (!IsRedirect(fd)) {
        m_pIoFile[fd] = (IAbsoluteIO *)new CConsoleFile();
        if (!m_pIoFile[fd]) {
            assert(0 && "CConsoleIo init: can't new CConsoleFile!\n");
            return E_OUT_OF_MEMORY;
        }
        m_pIoFile[fd]->AddRef();
        m_IsConsole[fd] = TRUE;
    }
    m_Offset[fd] = 0;

    return NOERROR;
}

IAbsoluteIO* CConsoleIo::GetIo(UInt32 fd)
{
    return m_pIoFile[fd];
}

Int32 CConsoleIo::Read()
{
    MemoryBuf_<256> buf;
    ECode ec = m_pIoFile[STDIN]->Read(m_Offset[STDIN], 256, &buf);
    if (FAILED(ec)) {
        return -1;
    }

    m_Offset[STDIN]++;
    buf[0] &= 0xFF;

    return buf[0];
}

ECode CConsoleIo::ReadLine(
    /* [out] */ AStringBuf *pasBuf)
{
    Int32 len, used, i;
    if (pasBuf == NULL || !(len = pasBuf->GetCapacity())) {
        return E_INVALID_ARGUMENT;
    }

    MemoryBuf buf((Byte *)pasBuf->GetPayload(), len);
    ECode ec = m_pIoFile[STDIN]->Read(m_Offset[STDIN], len, &buf);
    if (FAILED(ec)) {
        return ec;
    }

    used = buf.GetUsed();
    len = used;
    for (i = 0; i < used; i++) {
        if ((buf[i] == '\0') || (buf[i] == '\n') || (buf[i] == '\r')) {
            len = i + 1;
            break;
        }
    }

    m_Offset[STDIN] += len;
    if (!IsConsole(STDIN) && (i < used) && (buf[i] == '\r')) {
        if (i + 1 < used) {
            if (buf[i + 1] == '\n') {
                m_Offset[STDIN]++;
            }
        }
        else {
            MemoryBuf_<2> ch;
            ec = m_pIoFile[STDIN]->Read(m_Offset[STDIN], 1, &ch);
            if (FAILED(ec)) return ec;
            if (ch[0] == '\n') m_Offset[STDIN]++;
        }
    }

    pasBuf->SetLength(i);

    return NOERROR;
}

ECode CConsoleIo::Write(AString as)
{
    Int32 len = as.GetLength((INT_MAX - sizeof(wchar_t)) / sizeof(wchar_t));
    if (len == -1) return E_INVALID_ARGUMENT;

    Int32 count  = 0;
    char *p = NULL, *pDest = NULL;
    const char *pSource = as;
    if (!IsConsole(STDOUT)) {
        len = len * 2 + 1;
        p = (char *)_MALLOCA(len);
        pDest = p;

        if (*pSource == '\n') *pDest++ = '\r';
        *pDest++ = *pSource++;
        while (*pSource) {
            if ((*(pSource - 1) != '\r') && (*pSource == '\n')) {
                *pDest++ = '\r';
            }
            *pDest++ = *pSource++;
        }
        count = pDest - p;
    }
    else {
        p = (char *)pSource;
        count = len;
    }

    MemoryBuf array((Byte*)p, count);

    ECode ec = m_pIoFile[STDOUT]->Write(m_Offset[STDOUT], array, &count);
    if (!IsConsole(STDOUT)) _FREEA(p, len);
    if (FAILED(ec)) {
        return ec;
    }

    m_Offset[STDOUT] += count;

    return NOERROR;
}

Boolean CConsoleIo::IsConsole(UInt32 fd)
{
    if (fd > 2) return FALSE;
    return m_IsConsole[fd];
}

Boolean CConsoleIo::IsRedirect(UInt32 fd)
{
    ECode ec = NOERROR;
    wchar_t *pwszStdDevice;

    ec =  _EzGetStdDevice(fd, (WChar **)&pwszStdDevice);
    if (FAILED(ec) || pwszStdDevice == NULL) {
        return FALSE;
    }
    else {
        wchar_t fullname[_MAX_PATH];
        ec = WFullPath(fullname, pwszStdDevice, _MAX_PATH);
        if (FAILED(ec)) {
            goto rdExit;
        }

        WStringBuf_<_MAX_FNAME> wsbFile;
        IBasicDirectory* pDir = NULL;
        if (m_pDiskMgr == NULL) {
            ec = CSystem::FindRunningObject(L"diskmgr",
                (IInterface **)&m_pDiskMgr);
            if (FAILED(ec)) {
                goto rdExit;
            }
        }

        ec = m_pDiskMgr->AcquireDirInterface(fullname, &pDir, &wsbFile);
        if (FAILED(ec) || (wsbFile.GetLength() == 0)) {
            if (pDir) pDir->Release();
            goto rdExit;
        }

        IBasicFile *pFile = NULL;
        if (fd == 0) {
            ec = pDir->Lookup(wsbFile, &pFile);
            pDir->Release();
            if (FAILED(ec)) {
                goto rdExit;
            }
        }
        else {
            ec = pDir->CreateFile(wsbFile, FileAccessMode_Write,
                FALSE, &pFile);
            pDir->Release();
            if (FAILED(ec)) {
                goto rdExit;
            }
        }

        m_pIoFile[fd] = IAbsoluteIO::Probe(pFile); if (m_pIoFile[fd]) m_pIoFile[fd]->AddRef();
        pFile->Release();
        if (!m_pIoFile[fd]) {
            ec = E_NO_INTERFACE;
            goto rdExit;
        }

        m_IsConsole[fd] = FALSE;

        return TRUE;
    }
rdExit:
    _EzSetStdDevice(fd, NULL);
    return FALSE;
}
